import requests
import json
import os
import time
from typing import List, Dict, Optional, Tuple


class FeishuRobustFileSender:
    def __init__(self, app_id: str, app_secret: str, webhook_url: str):
        """初始化飞书健壮性文件发送器"""
        self.app_id = app_id
        self.app_secret = app_secret
        self.webhook_url = webhook_url
        self.base_headers = {"Content-Type": "application/json; charset=utf-8"}
        self.access_token: Optional[str] = None
        self.token_expiry: float = 0  # Token过期时间戳
        self._initialize_token()

    def _initialize_token(self) -> None:
        """确保Token有效，过期则刷新"""
        if not self.access_token or time.time() >= self.token_expiry:
            self.access_token = self._get_access_token()
            if self.access_token:
                # 设置Token有效期为1小时(3600秒)
                self.token_expiry = time.time() + 3600

    def _get_access_token(self) -> Optional[str]:
        """获取飞书API访问令牌"""
        url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"

        payload = {
            "app_id": self.app_id,
            "app_secret": self.app_secret
        }

        try:
            response = requests.post(
                url,
                headers=self.base_headers,
                data=json.dumps(payload),
                timeout=10
            )

            if response.status_code != 200:
                print(f"获取Token失败，HTTP状态码: {response.status_code}")
                return None

            try:
                result = response.json()
            except json.JSONDecodeError as e:
                print(f"解析Token响应失败: {str(e)}")
                return None

            if result.get("code") != 0:
                print(f"获取Token API错误: {result.get('msg')}")
                return None

            return result.get("tenant_access_token")

        except requests.exceptions.RequestException as e:
            print(f"获取Token网络错误: {str(e)}")
            return None

    def _get_upload_url(self, file_name: str, file_size: int) -> Optional[Dict[str, str]]:
        """获取单个文件的上传地址"""
        self._initialize_token()
        if not self.access_token:
            print("没有有效的访问令牌，无法继续")
            return None

        url = "https://open.feishu.cn/open-apis/im/v1/files"

        payload = {
            "file_name": file_name,
            "file_size": file_size,
            "file_type": "stream"
        }

        headers = {
            "Authorization": f"Bearer {self.access_token}",
            **self.base_headers
        }

        try:
            response = requests.post(
                url,
                headers=headers,
                data=json.dumps(payload),
                timeout=15
            )

            if response.status_code != 200:
                print(f"获取上传地址失败，状态码: {response.status_code}")
                return None

            try:
                result = response.json()
            except json.JSONDecodeError as e:
                print(f"解析上传地址响应失败: {str(e)}")
                return None

            if result.get("code") != 0:
                print(f"获取上传地址失败: {result.get('msg')}")
                return None

            return {
                "upload_url": result["data"]["upload_url"],
                "file_key": result["data"]["file_key"]
            }

        except requests.exceptions.RequestException as e:
            print(f"获取上传地址网络错误: {str(e)}")
            return None

    def _upload_single_file(self, file_path: str) -> Tuple[Optional[str], str]:
        """
        上传单个文件并返回结果
        :return: (file_key, 状态信息)
        """
        if not os.path.isfile(file_path):
            error_msg = "文件不存在"
            return None, error_msg

        file_name = os.path.basename(file_path)
        file_size = os.path.getsize(file_path)

        # 检查文件大小（飞书有200MB限制）
        if file_size > 200 * 1024 * 1024:
            error_msg = "文件超过200MB限制"
            return None, error_msg

        # 获取上传地址
        upload_info = self._get_upload_url(file_name, file_size)
        if not upload_info:
            error_msg = "无法获取上传地址"
            return None, error_msg

        # 上传文件内容
        try:
            with open(file_path, "rb") as file:
                response = requests.put(
                    upload_info["upload_url"],
                    data=file,
                    timeout=60
                )

            if 200 <= response.status_code < 300:
                return upload_info["file_key"], "上传成功"
            else:
                error_msg = f"上传失败，状态码: {response.status_code}"
                return None, error_msg

        except Exception as e:
            error_msg = f"上传出错: {str(e)}"
            return None, error_msg

    def send_message(self, message: str) -> bool:
        """独立发送文本消息，不受文件发送状态影响"""
        if not message.strip():
            print("消息内容为空，不发送")
            return True

        payload = {
            "msg_type": "text",
            "content": {
                "text": message
            }
        }

        try:
            response = requests.post(
                self.webhook_url,
                headers=self.base_headers,
                data=json.dumps(payload),
                timeout=10
            )

            try:
                result = response.json()
            except json.JSONDecodeError:
                print("发送消息响应不是有效的JSON")
                return False

            if result.get("code") != 0:
                error_msg = result.get("msg", "未知错误")
                print(f"发送消息失败: {error_msg}")
                return False
            else:
                print("消息发送成功")
                return True

        except requests.exceptions.RequestException as e:
            print(f"发送消息网络错误: {str(e)}")
            return False

    def send_files_with_message(self,
                                file_paths: List[str],
                                message: str = "",
                                send_message_first: bool = True,
                                delay_seconds: int = 2) -> Dict[str, str]:
        """
        发送文件和消息，文件发送失败不影响消息发送
        :param file_paths: 文件路径列表
        :param message: 要发送的消息
        :param send_message_first: 是否先发送消息
        :param delay_seconds: 文件之间的发送延迟
        :return: 上传结果字典 {文件路径: 状态}
        """
        results = {}

        # 确保消息发送不受文件影响，单独处理
        message_sent = False
        if message:
            # 可以选择先发送消息，再发送文件
            if send_message_first:
                message_sent = self.send_message(message)
            else:
                # 先标记，最后发送消息
                message_sent = None

        # 处理文件上传和发送
        if file_paths:
            print(f"开始处理 {len(file_paths)} 个文件...")
            for file_path in file_paths:
                file_name = os.path.basename(file_path)
                # 隔离每个文件的错误处理
                try:
                    file_key, status = self._upload_single_file(file_path)

                    if file_key:
                        # 发送文件到群组
                        payload = {
                            "msg_type": "file",
                            "content": {
                                "file_key": file_key
                            }
                        }

                        response = requests.post(
                            self.webhook_url,
                            headers=self.base_headers,
                            data=json.dumps(payload),
                            timeout=10
                        )

                        try:
                            result = response.json()
                        except json.JSONDecodeError:
                            results[file_path] = f"{file_name}: 发送失败(无效响应)"
                            continue

                        if result.get("code") != 0:
                            error_msg = result.get("msg", "未知错误")
                            results[file_path] = f"{file_name}: 发送失败({error_msg})"
                        else:
                            results[file_path] = f"{file_name}: 发送成功"
                    else:
                        results[file_path] = f"{file_name}: {status}"

                except Exception as e:
                    # 单个文件的异常不影响整体流程
                    results[file_path] = f"{file_name}: 处理异常({str(e)})"

                # 延迟处理下一个文件
                if file_path != file_paths[-1]:
                    time.sleep(delay_seconds)

        # 如果选择最后发送消息
        if message and message_sent is None:
            message_sent = self.send_message(message)

        return results

    def send_folder_with_message(self,
                                 folder_path: str,
                                 message: str = "",
                                 send_message_first: bool = True,
                                 delay_seconds: int = 2) -> Dict[str, str]:
        """发送文件夹中的所有文件和消息"""
        if not os.path.isdir(folder_path):
            print(f"无效的文件夹路径: {folder_path}")
            # 即使文件夹无效，仍尝试发送消息
            if message:
                self.send_message(message)
            return {}

        # 获取文件夹中所有文件
        file_paths = []
        for item in os.listdir(folder_path):
            item_path = os.path.join(folder_path, item)
            if os.path.isfile(item_path):
                file_paths.append(item_path)

        print(f"发现 {len(file_paths)} 个文件在文件夹 {folder_path} 中")
        return self.send_files_with_message(
            file_paths=file_paths,
            message=message,
            send_message_first=send_message_first,
            delay_seconds=delay_seconds
        )


if __name__ == "__main__":
    # 配置参数 - 请替换为实际值
    APP_ID = "cli_a80743c04ba6900c"  # 从飞书开放平台获取
    APP_SECRET = "DyVBgQneUG65cVj2tMvkut4iVJ4r5EkN"  # 从飞书开放平台获取
    WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/685c23ae-f945-4aa3-8a8f-8fb51e1b49a0"

    # 要发送的文件列表
    files_to_send = [
        "a.txt",
        "screenshot.png",
        "data.csv",
        "large_file.zip"  # 假设这个文件可能会发送失败
    ]

    # 初始化发送器
    sender = FeishuRobustFileSender(APP_ID, APP_SECRET, WEBHOOK_URL)

    # 发送文件和消息
    print("开始发送流程...")
    results = sender.send_files_with_message(
        file_paths=files_to_send,
        message="这是今天的测试相关文件，请查收。部分文件可能因大小限制无法发送。",
        send_message_first=True,  # 先发送消息，再发送文件
        delay_seconds=2
    )

    # 打印发送结果汇总
    print("\n发送结果汇总:")
    for file_path, status in results.items():
        print(f"- {status}")
